package compiler

import (
	"reflect"
	"strings"
	"testing"

	"tvl.fyi/tvix/eval/internal/index"
)

func assertEq[T comparable](t *testing.T, actual T, expected T, msg ...string) {
	if expected != actual {
		if len(msg) > 0 {
			t.Errorf("%s\nexpected: %v\nbut got: %v", strings.Join(msg, "\n"), expected, actual)
		} else {
			t.Errorf("%s value does not match!\nexpected: %v\nbut got: %v", reflect.TypeOf(expected), expected, actual)
		}
	}
}

func TestNamedLocal(t *testing.T) {
	var u compilationUnit
	idx, _ := u.declareLocal("foo")
	declared := u.locals[idx]

	assertEq(t, declared.name, "foo")
	assertEq(t, declared.depth, 0)
	assertEq(t, declared.used, false)
	assertEq(t, declared.initialised, false)

	assertEq(t, u.byName["foo"][0], idx)
}

func TestScopeEnding(t *testing.T) {
	var u compilationUnit

	// declare some outer locals
	u.declareLocal("outer1")
	u.declarePhantom()
	u.declareLocal("outer2")

	// begin inner scope
	u.beginScope()

	u.declareLocal("inner1")
	u.declarePhantom()
	u.declareLocal("inner2")

	assertEq(t, u.scopeDepth, 1, "scope depth should have increased")

	idx, ok := u.byName["inner1"]
	assertEq(t, ok, true, "inner1 should be declared by name")
	assertEq(t, idx[0], index.Local(3), "inner1 should be fourth local")

	inner1 := u.locals[idx[0]]

	assertEq(t, inner1.depth, 1, "inner1 should be at depth=1")

	// close the inner scope
	u.endScope()

	assertEq(t, len(u.locals), 3, "outer three locals should remain")
	for _, local := range u.locals {
		assertEq(t, local.depth, 0, "outer locals should all have depth 0")
	}

	idx, _ = u.byName["inner1"]
	assertEq(t, len(idx), 0, "inner1 should have been deleted")

	idx, _ = u.byName["outer1"]
	assertEq(t, len(idx), 1, "outer1 should remain")
}
